﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;

namespace LeanUi.ImageBundle
{
    [TaskName("cssinline")]
    public class CssInline : Task
    {
        private bool _inlineImages = true;
        private int _maxSpriteSize = 8;
        private FileSet _fileset = new FileSet();
        private string _extension = ".debug.css";
        private string _replacement = ".css";



        [TaskAttribute("extension"), StringValidator(AllowEmpty = false)]
        public string Extension
        {
            get { return _extension; }
            set { _extension = value; }
        }

        [TaskAttribute("replacement"), StringValidator(AllowEmpty = false)]
        public string Replacement
        {
            get { return _replacement; }
            set { _replacement = value; }
        }


        /// <summary>
        /// All the files in the file set will be deleted.
        /// </summary>
        [BuildElement("fileset")]
        public FileSet FileSet
        {
            get { return _fileset; }
            set { _fileset = value; }
        }

        [TaskAttribute("inlineimages")]
        public bool InlineImages
        {
            get { return _inlineImages; }
            set { _inlineImages = value; }
        }

        [TaskAttribute("maxsize"), Int32Validator(1, Int32.MaxValue)]
        public int MaxSpriteSize
        {
            get { return _maxSpriteSize; }
            set { _maxSpriteSize = value; }
        }


        protected override void ExecuteTask()
        {
            if (FileSet.BaseDirectory == null) {
                FileSet.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
            }

            foreach (string path in _fileset.FileNames)
            {
                Log(Level.Verbose, "filename: " + path);
                string txt = File.ReadAllText(path);
                string res = InlineCss(path, txt);
                File.WriteAllText(path.Replace(_extension,_replacement), res);
            }
        }

        public string InlineCss(string path, string txt)
        {
            List<Match> matches;
            do
            {
                matches = FindMatches(txt, "@import url(", ");");

                Log(Level.Debug, "Going to import "+ matches.Count +" css files");
                foreach (var match in matches) {
                    txt = txt.Replace(match.OutterText, GetInlineCss(match.InnerText, path));
                }
            }
            while (matches.Any());

            if ( InlineImages )
            {
                Log(Level.Debug, "Going to inline " + matches.Count + " image files");
                matches = FindMatches(txt, "url(", ")");
                foreach (var match in matches) {
                    txt = txt.Replace(match.OutterText, GetInlinePng(match.InnerText, path));
                }
            }
            return txt;
        }


        private string GetInlinePng(string input, string path)
        {
            input = input.Trim('\"', '\'');
            var fullpath = Path.Combine(Path.GetDirectoryName(path), input);
            
            return "url('"+ ( ToDataUri(fullpath) ?? input ) +"')";
        }
        
        private string ToDataUri(string filename)
        {
            if (File.Exists(filename))
            {
                using (var img = Image.FromFile(filename))
                using (var ms = new MemoryStream())
                {
                    img.Save(ms, ImageFormat.Png);
                    var bytes = ms.ToArray();

                    if (bytes.Length > (MaxSpriteSize*1024)) return null;
                    
                    var base64 = Convert.ToBase64String(bytes);
                    return "data:image/png;base64," + base64;
                }
            }
            return null;
        }

        private string GetInlineCss(string input, string path)
        {
            input = input.Trim('\"', '\'');
            var fullpath = Path.Combine(Path.GetDirectoryName(path), input);
            var inlinetxt = File.ReadAllText(fullpath);
            return inlinetxt;
        }

        private static List<Match> FindMatches(string source, string startToken, string endToken)
        {
            var matches = new List<Match>();
            int startPos = 0;

            while (startPos != -1)
            {
                startPos = source.IndexOf(startToken, startPos, StringComparison.InvariantCultureIgnoreCase);
                if ( startPos == -1 ) continue;
                var start = startPos + startToken.Length;
                var ende = source.IndexOf(endToken, start + 1, StringComparison.InvariantCultureIgnoreCase);
                startPos = ende;
                var inner = source.Substring(start, ende - start);
                var outter = startToken + inner + endToken;
                matches.Add(new Match() {
                                    Start = startPos, 
                                    End = ende, 
                                    InnerText = inner, 
                                    OutterText = outter
                                });

            }
            return matches;
        }

    }
    public class Match
    {
        public int Start;
        public int End;
        public string InnerText;
        public string OutterText;
    }
}